import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.net.*;
import java.io.*;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

public class AGServer{
	static ServerSocket serverSocket = null;
	static JTextArea jt = null;
	static PrintWriter txt;
	
	static List<MatchInfo> games = new ArrayList<MatchInfo>();
	static List<Player> players = new ArrayList<Player>();
	static List<Thread> threads = new ArrayList<Thread>();
	final static int playersInGame = 2;
	
	//A static inner class Runnable.
    private static class CacheCleaner implements Runnable{
        
        public void run()
        {
            txt.close();
            for (int i = 0; i < players.size(); i++){
            	players.get(i).closer();
            }
        }    
    }

    private static class Cache{
    	private CacheCleaner cleaner = new CacheCleaner();
    	
    	public Runnable getShutdownRunnable(){
    		return cleaner;
    	}
    }
	
	static class Player implements Runnable{
		Socket clientSckt = null;
		PrintWriter out = null;
		
		String nickname = "Unnamed_Player";
		int gameID = -1;
		
		Player(Socket client) throws IOException {
			clientSckt = client;
		}
		
		int getGameIndexByID(){
			int index = -1;
			for (int i = 0; i < games.size(); i++){
				if (games.get(i).getID() == this.gameID){
					index = i;
					break;
				}
			}
			return index;
		}
		
		public void closer(){
			String outputLine = "CloseSocket";
			outputLine = outputLine + "," + nickname;
			out.println(outputLine);
			jt.append("Server: " + outputLine + "\n");
			txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
			jt.append("\nSocket was closed. Player: " + this.nickname + "\n");
			txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "\nSocket was closed. Player: " + this.nickname);
			threads.remove(players.indexOf(this));
            players.remove(players.indexOf(this));
            games.get(getGameIndexByID()).deletePlayer(nickname);
            
            if (games.get(getGameIndexByID()).getPlayerCounter() == 0){
            	games.remove(getGameIndexByID());
            }
		}
		
		public void run(){
			try{
			out = new PrintWriter(clientSckt.getOutputStream(), true);
	        BufferedReader in = new BufferedReader(
					new InputStreamReader(
					clientSckt.getInputStream()));
	        String inputLine, outputLine;

	        boolean keepConnection = true;
	        while (((inputLine = in.readLine()) != null) && (keepConnection)){
	        	jt.append(nickname + ": " + inputLine + "\n");
	        	out.println(nickname + ": " + inputLine);
	        	txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + nickname + ": " + inputLine);
	        	//jt.setCaretPosition(jt.getDocument().getLength());
	        	String[] request = inputLine.split(",");
	             
	             if (request[0].equals("InitGame")){
	            	 
	            	 if (games.size() == 0){
	            		 Random rn = new Random();
	            		 MatchInfo newGame = new MatchInfo();
	            		 newGame.setID(rn.nextInt());
	            		 games.add(newGame);
	            		 gameID = newGame.getID();
	            	 }
	            	 
	            	 if ((gameID == -1) && (games.get(games.size() - 1).getPlayerCounter() >= playersInGame)){
	            		MatchInfo newGame = new MatchInfo();
	            		Random rn = new Random();
	             		
	             		boolean gotNewID = false;
	             		while (!gotNewID){
	             			gotNewID = true;
	             			int newID = rn.nextInt();
	             			
	             			for (int i = 0; i < games.size(); i++){
	                 			if (games.get(i).getID() == newID){
	                 				gotNewID = false;
	                 				break;
	                 			}
	                 		}
	             			
	             			if (gotNewID){
	             				newGame.setID(newID);
	             			}
	             		}
	             		games.add(newGame);
	             		gameID = newGame.getID();
	            	 } else {
	            		 gameID = games.get(games.size() - 1).getID();
	            	 }
	            	 
	            	 if (nickname == "Unnamed_Player"){
	            		 nickname = request[1];
		            	 games.get(getGameIndexByID()).addPlayer(nickname);
	            	 }
	            	 
	            	 List<String> playersList = games.get(getGameIndexByID()).getPlayers();
	            	 outputLine = "InitGame";
	            	 for (int i = 0; i < playersList.size(); i++){
	            		 outputLine = outputLine + ",";
	            		 outputLine = outputLine + playersList.get(i);
	            	 }
	            	 out.println(outputLine);
	            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
	            	 jt.setCaretPosition(jt.getDocument().getLength());
	             } else
	             
	             if (request[0].equals("DoMove")){
	            	 games.get(getGameIndexByID()).doMove(request[1], Integer.parseInt(request[2]), 
	            			 Integer.parseInt(request[3]));
	            	 outputLine = "DoMove";
	            	 outputLine = outputLine + "," + nickname;
	            	 out.println(outputLine);
	            	 jt.append("Server: " + outputLine + "\n");
	            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
	             } else
	             
	             if (request[0].equals("HisMove")){
	            	 if (games.get(getGameIndexByID()).heInGame(request[1])){
	            		 String hisX = String.valueOf(games.get(getGameIndexByID()).getMove(request[1]).getX());
		            	 String hisY = String.valueOf(games.get(getGameIndexByID()).getMove(request[1]).getY());
		            	 outputLine = "HisMove";
		            	 outputLine = outputLine + "," + request[1];
		            	 outputLine = outputLine + "," + hisX;
		            	 outputLine = outputLine + "," + hisY;
		            	 out.println(outputLine);
		            	 jt.append("Server: " + outputLine + "\n");
		            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
	            	 } else {
	            		 keepConnection = false; // Trash
		            	 outputLine = "CloseSocket";
		            	 outputLine = outputLine + "," + nickname;
		            	 out.println(outputLine);
		            	 jt.append("Server: " + outputLine + "\n");
		            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
		            	 jt.append("\nSocket was closed. Player: " + this.nickname + "\n");
		            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "\nSocket was closed. Player: " + this.nickname);
		 	             threads.remove(players.indexOf(this));
		 	             players.remove(players.indexOf(this));
		 	             games.get(getGameIndexByID()).deletePlayer(nickname);
		 	             
		 	             if (games.get(getGameIndexByID()).getPlayerCounter() == 0){
		 	             	games.remove(getGameIndexByID());
		 	             }
		            	 
		            	 break;
	            	 }
	            	 
	             } else
	            	 
	             if (request[0].equals("CloseSocket")){
	            	 keepConnection = false; // Trash
	            	 outputLine = "CloseSocket";
	            	 outputLine = outputLine + "," + nickname;
	            	 out.println(outputLine);
	            	 jt.append("Server: " + outputLine + "\n");
	            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Server: " + outputLine);
	            	 jt.append("\nSocket was closed. Player: " + this.nickname + "\n");
	            	 txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "\nSocket was closed. Player: " + this.nickname);
	 	             threads.remove(players.indexOf(this));
	 	             players.remove(players.indexOf(this));
	 	             games.get(getGameIndexByID()).deletePlayer(nickname);
	 	             
	 	             if (games.get(getGameIndexByID()).getPlayerCounter() == 0){
	 	             	games.remove(getGameIndexByID());
	 	             }
	            	 
	            	 break;
	             } else
	            	 
	             {
	            	 // TODO: Received unknown request
	             }
	        	
	        }
	        out.close();
	        in.close();
	        clientSckt.close();
			} catch (IOException e) {
	          	jt.append("\nSocket was closed. Player: " + this.nickname + "\n");
	          	txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "\nSocket was closed. Player: " + this.nickname + "\n");
	            threads.remove(players.indexOf(this));
	            players.remove(players.indexOf(this));
	            games.get(getGameIndexByID()).deletePlayer(nickname);
	            
	            if (games.get(getGameIndexByID()).getPlayerCounter() == 0){
	            	games.remove(getGameIndexByID());
	            }
	        }
		}
	}
	
	public static String localtime(String dateFormat) {
	    Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
	    return sdf.format(cal.getTime());

	  }
	
	public static void main(String[] args) throws IOException {
		Cache cache = new Cache();
		Thread hook = new Thread(cache.getShutdownRunnable());
	    Runtime.getRuntime().addShutdownHook(hook);
		txt = new PrintWriter(new FileWriter("log.txt"));
		JFrame frame= new JFrame("AltoGo Server");
	    JPanel panel=new JPanel();
	    jt= new JTextArea(27,46);
	    JScrollPane pScroll = new JScrollPane(jt, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
	    jt.setAutoscrolls(true);
	    frame.add(panel);
	    panel.add(pScroll);
	    frame.setSize(550,500);
	    frame.setVisible(true);
	    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		Player newPlayer;
		
        try {
            serverSocket = new ServerSocket(6382);
        } catch (IOException e) {
            jt.append("Error - Could not listen on port: 6382." + "\n");
            txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Error - Could not listen on port: 6382.");
            txt.close();
            System.exit(1);
        }

        boolean tmpFlag = true;
        while (tmpFlag){
        	Socket clientSocket = null;
            try {
                clientSocket = serverSocket.accept();
            } catch (IOException e) {
                jt.append("Error: Accept failed - Socket accepting." + "\n");
                txt.println(localtime("dd.MM.yyyy kk:mm") + ": " + "Error - Could not listen on port: 6382.");
                txt.close();
                System.exit(1);
            }
            
            newPlayer = new Player(clientSocket);
            players.add(newPlayer);
            threads.add(new Thread(newPlayer));
            threads.get(threads.size() - 1).start();
        }
        serverSocket.close();
	}
}
